`include "cpu_def.vh"

module execute(
  input clk,
  input rst,

  // from de_ex_reg
  input                   valid_i        ,
  input [            3:0] stall_i        ,
  input [           31:0] pc_i           ,
  input                   br_pre_taken_i ,
  input [           31:0] br_pre_pc_i    ,
  input [           31:0] rf_rdata_0_i   ,
  input [           31:0] rf_rdata_1_i   ,
  input [           31:0] hl_rdata_i     ,
  input [           31:0] cp0_rdata_i    ,
  input [            4:0] rs_i           ,
  input [            4:0] rt_i           ,
  input [            4:0] rd_i           ,
  input [            4:0] sa_i           ,
  input [           31:0] simm_i         ,
  input [           31:0] uimm_i         ,
  input [           31:0] addr_i         ,
  input [           12:0] alu_op_i       ,
  input                   sel_alu_src_0_i,
  input [            1:0] sel_alu_src_1_i,
  input [            1:0] sel_rf_waddr_i ,
  input [            1:0] sel_br_target_i,
  input                   sel_hl_wdata_ex_i,
  input                   read_mem_i     ,
  input                   write_mem_i    ,
  input                   branch_i       ,
  input [`NR_LD_OP - 1:0] ld_op_i        ,
  input [`NR_ST_OP - 1:0] st_op_i        ,
  input [`NR_BR_OP - 1:0] br_op_i        ,
  input                   mul_i          ,
  input                   mul_sign_i     ,
  input                   div_i          ,
  input                   div_sign_i     ,
  input                   chk_ov_i       ,
  input [           31:0] br_target_i    ,
  input [           31:0] jp_target_i    ,

  input                   write_rf_i     ,
  input [            2:0] sel_rf_wdata_i ,
  input [            1:0] write_hl_i     ,
  input                   sel_hl_wdata_i ,
  input [            2:0] cp0_wsel_i     ,
  input                   cp0_wen_i      ,
  input                   ft_tlbre_i     ,
  input                   ft_tlbi_i      ,
  input                   ft_adel_i      ,
  input                   sys_i          ,
  input                   brk_i          ,
  input                   adel_i         ,
  input                   ades_i         ,
  input                   tlbrel_i       ,
  input                   tlbres_i       ,
  input                   tlbil_i        ,
  input                   tlbis_i        ,
  input                   tlbm_i         ,
  input                   ri_i           ,
  input                   int_i          ,
  input [           31:0] badvaddr_i     ,
  input                   eret_i         ,
  input                   tlbp_i         ,
  input                   tlbr_i         ,
  input                   tlbwi_i        ,
  input                   tlbwr_i        ,
  input                   lsu_stall_ex_i ,

  // to wb
  output        valid_o       ,
  output [31:0] pc_o          ,
  output [31:0] alu_result_o  ,
  output [63:0] div_rs_o      ,
  output        wr_mem_o      ,

  output        write_rf_o    ,
  output [ 4:0] rf_waddr_o    ,
  output [ 2:0] sel_rf_wdata_o,

  output [ 1:0] write_hl_o    ,
  output        sel_hl_wdata_o,

  output [ 2:0] cp0_wsel_o    ,
  output [ 4:0] cp0_wreg_num_o,
  output        cp0_wen_o     ,
  output [31:0] cp0_wdata_o   ,
  output [31:0] cp0_rdata_o   ,
  output        bd_o          ,
  output        ft_tlbre_o    ,
  output        ft_tlbi_o     ,
  output        sys_o         ,
  output        brk_o         ,
  output        ov_o          ,
  output        adel_o        ,
  output        ades_o        ,
  output        tlbrel_o      ,
  output        tlbres_o      ,
  output        tlbil_o       ,
  output        tlbis_o       ,
  output        tlbm_o        ,
  output        ft_adel_o     ,
  output        ri_o          ,
  output        int_o         ,
  output [31:0] badvaddr_o    ,
  output        eret_o        ,
  output        tlbp_o        ,
  output        tlbr_o        ,
  output        tlbwi_o       ,
  output        tlbwr_o       ,

  // to backend_redirect
  output        ex_redirect_taken,
  output [31:0] ex_redirect_pc   ,

  // to frontend
  output branch,
  output branch_taken,
  output [31:0] branch_pc,
  output [31:0] branch_target_pc,

  // to forward path
  output                       ex_rf_wen      ,
  output                [ 4:0] ex_rf_wnum     ,
  output                [31:0] ex_rf_wdata    ,

  // to de stage
  output                       ex_ld          ,
  output                       ex_mul         ,

  // to mul
  output                [31:0] mul_x          ,
  output                [31:0] mul_y          ,
  output                       mul_sign       ,

  // to lsu
  output                       lsu_valid      ,
  output                       lsu_read_mem   ,
  output                       lsu_write_mem  ,
  output [`NR_LD_OP - 1    :0] lsu_ld_op      ,
  output [`NR_ST_OP - 1    :0] lsu_st_op      ,
  output                [31:0] lsu_addr       ,
  output                [31:0] lsu_rt         ,
  output                       lsu_ex_taken   ,

  // to hazard
  output stall_req_o ,
  output flush_req_o ,

  // from wb
  input wb_ex_taken
);

  wire        bj_br_taken;
  wire        br_taken   ;
  wire [31:0] br_ds_pc   ;
  wire [31:0] br_pc      ;

  wire [31:0] alu_src_0   ;
  wire [31:0] alu_src_1   ;
  wire [12:0] alu_op      ;
  wire        alu_overflow;
  wire        alu_carryout;
  wire        alu_zero    ;
  wire [31:0] alu_result  ;
  wire [31:0] alu_addr    ;

  wire [31:0] div_x       ;
  wire [31:0] div_y       ;
  wire        div         ;
  wire        div_signed  ;
  wire [31:0] div_s       ;
  wire [31:0] div_r       ;
  wire        div_complete;
  wire [63:0] div_rs      ;

  wire [5:0] wb_rf_waddr;

  reg [31:0] br_delay_pc;
  reg        br_valid;
  reg [31:0] br_instrs;
  reg [31:0] br_mispre;
  reg br_taken_r;
  reg [31:0] br_target_r;

  // handle sxception
  reg invalid;

  always@(posedge clk) begin
    if (rst) begin
      br_instrs <= 0;
    end else if (branch_i && valid_i && !stall_i[1]) begin
      br_instrs <= br_instrs + 1;
    end
  end

  always@(posedge clk) begin
    if (rst) begin
      br_mispre <= 0;
    end else if (ex_redirect_taken && valid_i && !stall_i[1]) begin
      br_mispre <= br_mispre + 1;
    end
  end

  always @(posedge clk) begin
    if (rst || wb_ex_taken) begin
      invalid <= 1'b0;
    end else if (ex_taken && valid_i && !stall_i[1]) begin
      invalid <= 1'b1;
    end
  end

  wire div_incomplete = div && !div_complete;
  wire ex_taken = (
    ft_tlbre_o ||
    ft_tlbi_o  ||
    sys_o      ||
    brk_o      ||
    ov_o       ||
    adel_o     ||
    ades_o     ||
    tlbrel_o   ||
    tlbres_o   ||
    tlbil_o    ||
    tlbis_o    ||
    tlbm_o     ||
    ft_adel_o  ||
    ri_o       ||
    int_o  
  );
  wire eret_taken = eret_o;
  assign stall_req_o = (div_incomplete || lsu_stall_ex_i) && valid_o;
  // assign flush_req_o = (ex_redirect_taken || ex_taken || eret_taken || tlbr_o || tlbwi_o || tlbp_o) && valid_o;
  assign flush_req_o = (ex_redirect_taken || eret_taken || tlbr_o || tlbwi_o || tlbwr_o || tlbp_o) && valid_o;
  
  assign alu_src_0 = sel_alu_src_0_i ? {27'd0, sa_i} : 
                                        rf_rdata_0_i ;
  assign alu_src_1 = sel_alu_src_1_i[1] ? 
                    (sel_alu_src_1_i[0] ? hl_rdata_i : uimm_i      ) :
                    (sel_alu_src_1_i[0] ? simm_i     : rf_rdata_1_i) ;                                        
  assign alu_op    = alu_op_i;

  alu mycpu_alu(
    .alu_src0(alu_src_0),
    .alu_src1(alu_src_1),
    .alu_op  (alu_op   ),

    .alu_overflow(alu_overflow),
    .alu_carryout(alu_carryout),
    .alu_zero    (alu_zero    ),
    .alu_result  (alu_result  ),
    .alu_addr    (alu_addr    )
  );

  assign div_x      = rf_rdata_0_i;
  assign div_y      = rf_rdata_1_i;
  assign div        = div_i && valid_o && !ex_taken;
  assign div_signed = div_sign_i;
  assign div_rs     = sel_hl_wdata_ex_i ? 
    {div_r                               , div_s                               } : 
    {write_hl_i[1] ? rf_rdata_0_i : 32'd0, write_hl_i[0] ? rf_rdata_0_i : 32'd0} ;

  div mycpu_div(
    .div_clk (clk),
    .rst  (rst),
    .div(div),
    .div_signed (div_signed),
    .x(div_x),
    .y(div_y),

    .s(div_s),
    .r(div_r),
    .complete(div_complete)
  );

  branch_judge mycpu_branch_judge(
    .valid       (valid_o     ),
    .br_op       (br_op_i     ),
    .rf_rdata_0  (rf_rdata_0_i),
    .rf_rdata_1  (rf_rdata_1_i),

    .br_taken    (bj_br_taken)
  );

  assign bd_o = (pc_i == br_delay_pc) && br_valid;
  always@(posedge clk) begin
    if (rst) begin
      br_valid <= 0;
    end else if (branch_i && valid_i) begin
      br_valid <= 1;
    end if (pc_i == br_delay_pc && !stall_i[1] && valid_i) begin
      br_valid <= 0;
    end
  end
  always@(posedge clk) begin
    if (branch_i && valid_i) begin
      br_delay_pc <= br_ds_pc;
    end
  end
  always@(posedge clk) begin
    if (branch_i && valid_i) begin
      br_taken_r <= br_taken;
      br_target_r <= br_pc;
    end
  end

  assign wb_rf_waddr = sel_rf_waddr_i[1] ? 
                      (sel_rf_waddr_i[0] ? 5'd0 : 5'h1f) :
                      (sel_rf_waddr_i[0] ? rt_i : rd_i ) ;

  assign valid_o        = valid_i && !invalid;
  assign pc_o           = pc_i          ;
  assign alu_result_o   = alu_result    ;
  assign div_rs_o       = div_rs        ;
  assign wr_mem_o       = read_mem_i || write_mem_i;
  assign write_rf_o     = write_rf_i    ;
  assign rf_waddr_o     = wb_rf_waddr   ;
  assign sel_rf_wdata_o = sel_rf_wdata_i;
  assign write_hl_o     = write_hl_i & {2{valid_o}};
  assign sel_hl_wdata_o = sel_hl_wdata_i;
  assign cp0_wsel_o     = cp0_wsel_i    ;
  assign cp0_wreg_num_o = rd_i          ;
  assign cp0_wen_o      = cp0_wen_i && valid_o;
  assign cp0_wdata_o    = rf_rdata_1_i  ;
  assign cp0_rdata_o    = cp0_rdata_i   ;
  assign ft_tlbre_o     = ft_tlbre_i    ;
  assign ft_tlbi_o      = ft_tlbi_i     ;
  assign sys_o          = sys_i         ;
  assign brk_o          = brk_i         ;
  assign ov_o           = chk_ov_i && alu_overflow;
  assign adel_o         = adel_i        ;
  assign ades_o         = ades_i        ;
  assign tlbrel_o       = tlbrel_i      ;
  assign tlbres_o       = tlbres_i      ;
  assign tlbil_o        = tlbil_i       ;
  assign tlbis_o        = tlbis_i       ;
  assign tlbm_o         = tlbm_i        ;
  assign ft_adel_o      = ft_adel_i     ;
  assign ri_o           = ri_i          ;
  assign int_o          = int_i         ;
  assign badvaddr_o     = badvaddr_i    ;
  assign eret_o         = eret_i        ;
  assign tlbp_o         = tlbp_i        ;
  assign tlbr_o         = tlbr_i        ;
  assign tlbwi_o        = tlbwi_i       ;
  assign tlbwr_o        = tlbwr_i       ;

  // deal with branch prediction miss
  assign br_pc = sel_br_target_i[1] ? 
                (sel_br_target_i[0] ? 32'd0       : rf_rdata_0_i) : 
                (sel_br_target_i[0] ? jp_target_i : br_target_i ) ;
  assign br_ds_pc = pc_i + 4;
  assign br_taken = bj_br_taken && !stall_i[1];

  assign ex_redirect_taken = valid_o && !stall_i[1] && (
    (
      (br_taken_r ^ br_pre_taken_i) || 
      (br_taken_r && br_pre_taken_i && (br_target_r != br_pre_pc_i))
    ) && bd_o ||
    (
      br_pre_taken_i
    ) && !bd_o
  );
  assign ex_redirect_pc = ((br_taken_r && br_pre_taken_i && (br_target_r != br_pre_pc_i) || (br_taken_r && !br_pre_taken_i)) && bd_o )? br_target_r : br_ds_pc;

  assign branch = valid_o && !stall_i[1] && bd_o;
  assign branch_taken = br_taken_r;
  assign branch_pc = pc_i;
  assign branch_target_pc = br_target_r;

  assign ex_rf_wen   = write_rf_i && valid_o;
  assign ex_rf_wnum  = wb_rf_waddr         ;
  assign ex_rf_wdata = (sel_rf_wdata_i[0] && sel_rf_wdata_i[1]) ? cp0_rdata_i : alu_result          ;

  assign ex_ld = read_mem_i && valid_o;
  assign ex_mul = mul_i && valid_o;

  assign mul_x    = rf_rdata_0_i;
  assign mul_y    = rf_rdata_1_i;
  assign mul_sign = mul_sign_i;

  assign lsu_valid     = valid_o     ;
  assign lsu_read_mem  = read_mem_i  ;
  assign lsu_write_mem = write_mem_i ;
  assign lsu_ld_op     = ld_op_i     ;
  assign lsu_st_op     = st_op_i     ;
  assign lsu_addr      = addr_i      ;
  // assign lsu_addr      = alu_addr    ;
  assign lsu_rt        = rf_rdata_1_i;
  assign lsu_ex_taken  = ex_taken    ;

endmodule
